/**
 * 网络服务模块 - 封装Fetch API，提供离线支持和请求重试功能
 */

import requestQueue from './request-queue.js';
import errorHandler, { ErrorType, ErrorSeverity } from './error-handler.js';
import { requestBackgroundSync } from './service-worker.js';
// 导入network-status模块的isOnline函数，避免代码重复
import { isOnline } from './network-status.js';

// 重新导出isOnline函数，保持向后兼容性
export { isOnline };

/**
 * 超时获取数据
 * @param {string} url - 请求URL
 * @param {Object} options - fetch选项
 * @param {number} timeout - 超时时间（毫秒）
 * @returns {Promise<any>}
 */
export function fetchWithTimeout(url, options = {}, timeout = 10000) {
  return new Promise((resolve, reject) => {
    const controller = new AbortController();
    const signal = controller.signal;
    const timeoutId = setTimeout(() => {
      controller.abort();
      reject(new Error(`请求超时: ${url}`));
    }, timeout);

    fetch(url, { ...options, signal })
      .then(response => {
        clearTimeout(timeoutId);
        if (!response.ok) {
          const error = new Error(`HTTP错误: ${response.status}`);
          error.statusCode = response.status;
          throw error;
        }
        return response.json();
      })
      .then(resolve)
      .catch(error => {
        clearTimeout(timeoutId);
        reject(error);
      });
  });
}

/**
 * 检查响应是否成功
 * @param {Response} response - fetch API的响应对象
 * @returns {Response} 如果成功则返回响应，否则抛出错误
 */
function checkResponseStatus(response) {
  if (!response.ok) {
    throw new Error(`HTTP错误! 状态码: ${response.status}`);
  }
  return response;
}

/**
 * 处理JSON响应
 * @param {Response} response - fetch API的响应对象
 * @returns {Promise<Object>} 解析后的JSON数据
 */
async function parseJsonResponse(response) {
  const contentType = response.headers.get('content-type');
  if (contentType && contentType.includes('application/json')) {
    try {
      return await response.json();
    } catch (error) {
      console.error('解析JSON响应失败:', error);
      throw new Error('无效的JSON响应');
    }
  }
  return null;
}

/**
 * 带重试的请求
 * @param {string} url - 请求URL
 * @param {Object} options - 请求选项
 * @returns {Promise<any>}
 */
export async function retryRequest(url, options = {}) {
  const { retryOptions = {} } = options;
  const maxRetries = retryOptions.maxRetries || 3;
  const retryDelay = retryOptions.retryDelay || 1000;
  const exponentialBackoff = retryOptions.exponentialBackoff || false;
  const retryableStatusCodes = retryOptions.retryableStatusCodes || [408, 429, 500, 502, 503, 504];
  
  let lastError;
  
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      // 如果是重试，添加重试头
      const retryOptionsForAttempt = attempt > 0 
        ? { ...options.headers, 'X-Retry-Attempt': attempt }
        : options.headers;
      
      // 设置当前的请求选项
      const currentOptions = {
        ...options,
        headers: retryOptionsForAttempt
      };
      
      // 检查是否是在离线状态
      if (!await isOnline()) {
        if (options.skipQueue !== true) {
          // 离线状态下，将请求加入队列
          return enqueueRequest(url, currentOptions);
        }
        throw new Error('离线状态，请求被跳过队列');
      }
      
      // 执行请求，添加超时处理
      const response = await fetchWithTimeout(url, currentOptions, options.timeout || 10000);
      
      // 请求成功
      return response;
    } catch (error) {
      // 记录错误
      console.warn(`请求尝试 ${attempt + 1} 失败:`, error);
      lastError = error;
      
      // 判断错误是否可以重试
      const canRetry = error.name === 'AbortError' || // 超时错误可以重试
                      (error.statusCode && retryableStatusCodes.includes(error.statusCode)); // 特定状态码可以重试
      
      // 如果是最后一次尝试，或者错误不可重试，抛出错误
      if (attempt === maxRetries || (!canRetry && error.statusCode)) {
        break;
      }
      
      // 否则，等待后重试
      // 计算延迟时间，如果启用了指数退避，则延迟时间呈指数增长
      const currentDelay = exponentialBackoff 
        ? retryDelay * Math.pow(2, attempt) 
        : retryDelay;
      
      await new Promise(resolve => setTimeout(resolve, currentDelay));
    }
  }
  
  // 所有尝试都失败了，使用错误处理模块记录
  errorHandler.handleError(
    lastError,
    ErrorType.NETWORK,
    ErrorSeverity.ERROR,
    { context: '网络请求', url, attempts: maxRetries + 1 }
  );
  
  throw lastError;
}

/**
 * 将请求加入队列并请求后台同步
 * @param {string} url - 请求URL
 * @param {Object} options - 请求选项
 * @returns {Promise<string>} 请求ID
 */
async function enqueueRequest(url, options = {}) {
  try {
    const requestId = await requestQueue.addToQueue(url, options);
    
    // 请求后台同步
    try {
      await requestBackgroundSync('apiQueue');
    } catch (syncError) {
      console.warn('请求后台同步失败，但请求已加入队列:', syncError);
    }
    
    return requestId;
  } catch (error) {
    console.error('将请求加入队列失败:', error);
    throw error;
  }
}

/**
 * 包装后的GET请求
 * @param {string} url - 请求URL
 * @param {Object} options - 请求选项
 * @returns {Promise} 请求结果
 */
export async function get(url, options = {}) {
  const fetchOptions = {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json',
      ...options.headers
    },
    credentials: options.credentials || 'same-origin',
    ...options
  };

  return retryRequest(url, fetchOptions);
}

/**
 * 包装后的POST请求
 * @param {string} url - 请求URL
 * @param {Object} data - 请求数据
 * @param {Object} options - 请求选项
 * @returns {Promise} 请求结果
 */
export async function post(url, data = null, options = {}) {
  const fetchOptions = {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      ...options.headers
    },
    credentials: options.credentials || 'same-origin',
    body: data ? JSON.stringify(data) : null,
    ...options
  };

  return retryRequest(url, fetchOptions);
}

/**
 * 包装后的PUT请求
 * @param {string} url - 请求URL
 * @param {Object} data - 请求数据
 * @param {Object} options - 请求选项
 * @returns {Promise} 请求结果
 */
export async function put(url, data = null, options = {}) {
  const fetchOptions = {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json',
      ...options.headers
    },
    credentials: options.credentials || 'same-origin',
    body: data ? JSON.stringify(data) : null,
    ...options
  };

  return retryRequest(url, fetchOptions);
}

/**
 * 包装后的DELETE请求
 * @param {string} url - 请求URL
 * @param {Object} options - 请求选项
 * @returns {Promise} 请求结果
 */
export async function del(url, options = {}) {
  const fetchOptions = {
    method: 'DELETE',
    headers: {
      'Content-Type': 'application/json',
      ...options.headers
    },
    credentials: options.credentials || 'same-origin',
    ...options
  };

  return retryRequest(url, fetchOptions);
}

/**
 * 通用的网络请求方法
 * @param {string} url - 请求URL
 * @param {Object} options - 请求选项
 * @returns {Promise} 请求结果
 */
export async function request(url, options = {}) {
  // 设置默认请求选项
  const fetchOptions = {
    method: options.method || 'GET',
    headers: {
      'Content-Type': 'application/json',
      ...options.headers
    },
    credentials: options.credentials || 'same-origin',
    ...options
  };

  // 如果请求方法不是GET且有数据，则序列化数据
  if (fetchOptions.method !== 'GET' && options.body && typeof options.body === 'object') {
    fetchOptions.body = JSON.stringify(options.body);
  }

  return retryRequest(url, fetchOptions);
}

/**
 * 获取请求队列状态
 * @returns {Promise<Object>} 队列状态信息
 */
export async function getQueueStatus() {
  try {
    const queue = await requestQueue.getQueue();
    const pendingRequests = queue.filter(req => req.status === 'pending');
    const failedRequests = queue.filter(req => req.status === 'failed');
    const completedRequests = queue.filter(req => req.status === 'completed');

    return {
      total: queue.length,
      pending: pendingRequests.length,
      failed: failedRequests.length,
      completed: completedRequests.length
    };
  } catch (error) {
    console.error('获取请求队列状态失败:', error);
    return {
      error: error.message
    };
  }
}

/**
 * 清除请求队列
 * @returns {Promise<void>}
 */
export async function clearRequestQueue() {
  try {
    await requestQueue.clearQueue();
  } catch (error) {
    console.error('清除请求队列失败:', error);
    throw error;
  }
}

// 导出默认对象
const networkService = {
  isOnline,
  get,
  post,
  put,
  del,
  request,
  getQueueStatus,
  clearRequestQueue
};

export default networkService;